<?php

namespace app\common\controller;

use app\BaseController;
use PhpOffice\PhpSpreadsheet\IOFactory;
use think\App;
use getID3;

class Upload extends BaseController
{
    private $upload; // 上传类

    function __construct(App $app){
        parent::__construct($app);
        // 实例化上传类
        $upload = new \app\common\Upload();
        // 设置上传类型和文件大小
        $this->upload = $upload->defaultConfig();
    }

    /**
     * 获取分片上传ID-（ossV2）
     * @return bool|string
     */
    public function getV2UploadId(): bool|string
    {
        try {
            $param = $this->request->param();
            $this->validate($param,[
                'ext|文件后缀' => 'require'
            ]);
            $ext = explode('.',$param['ext']);
            $save_name = 'upload/'.date('Ym').'/'.date('d').'_'.time().uniqid().'.'.$ext[count($ext)-1];
            $OssV2Upload = new \app\common\OssV2Upload();
            $initResult = $OssV2Upload->getUploadId($save_name);
            return success([
                'upload_id' => $initResult->uploadId,
                'save_name' => $save_name
            ]);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传分片
     * @param $file
     * @param $upload_id
     * @param $num
     * @param $save_name
     * @return bool|string
     */
    public function ossV2ChunkUpload($file,$upload_id,$num,$save_name): bool|string
    {
        try {
            $OssV2Upload = new \app\common\OssV2Upload();
            $partResult = $OssV2Upload->chunkUpload($file,$upload_id,$num,$save_name);
            return success($partResult->etag);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 完成分片上传
     * @param $upload_id
     * @param $part
     * @param $save_name
     * @return bool|string
     */
    public function ossV2CompleteMultipartUpload($upload_id,$part,$save_name): bool|string
    {
        try {
            $OssV2Upload = new \app\common\OssV2Upload();
            $comResult = $OssV2Upload->completeMultipartUpload($upload_id,$part,$save_name);
            return success([
                'path' => $comResult->location
            ]);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传binary文件-pdf
     */
    public function upload_binary_file(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传单文件
            if (is_array($files)){
                $src = [];
                foreach ($files as $file){
                    $src[] = $this->upload->upload(file_get_contents($file->getRealPath()),'binary','.pdf');
                }
            }else{
                $src = $this->upload->upload(file_get_contents($files->getRealPath()),'binary','.pdf');
            }
            return success($src);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传base64图片
     */
    public function upload_base64_file(): bool|string
    {
        try {
            $files = $this->request->param('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传单文件
            if (is_array($files)){
                $src = [];
                foreach ($files as $file){
                    $src[] = $this->upload->upload($file,'base64','.png');
                }
            }else{
                $src = $this->upload->upload($files,'base64','.png');
            }
            return success($src);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传图片
     */
    public function upload_file(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传操作
            if(is_array($files)) {
                // 上传多文件
                $src = [];
                foreach ($files as $file) {
                    $src[] = $this->upload->upload(file_get_contents($file),'binary','.png');
                }
            }else {
                // 上传单文件
                $src = $this->upload->upload(file_get_contents($files),'binary','.png');
            }
            return success($src);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传量表文件
     * @return false|string
     */
    public function uploadRatingScaleFile(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传操作
            if(is_array($files)) {
                // 上传多文件
                $data = [];
                foreach ($files as $file) {
                    $src = $this->upload->upload(file_get_contents($file),'binary','.xlsx');
                    $spreadSheet = IOFactory::load($file->getRealPath());
                    $sheet = $spreadSheet->getSheet(0);
                    $RatingScaleName = $sheet->getCell('B2')->getValue();
                    $data[] = ['src' => $src, 'rating_scale_name' => $RatingScaleName];
                }
            }else {
                // 上传单文件
                $src = $this->upload->upload(file_get_contents($files),'binary','.xlsx');
                $spreadSheet = IOFactory::load($files->getRealPath());
                $sheet = $spreadSheet->getSheet(0);
                $RatingScaleName = $sheet->getCell('B2')->getValue();
                $data = ['src' => $src, 'rating_scale_name' => $RatingScaleName];
            }
            return success($data);
        } catch (\Exception $e){
            return error($e->getMessage(),$e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传用户文件
     * @return false|string
     */
    public function uploadUserFile(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传操作
            if(is_array($files)) {
                // 上传多文件
                $src = [];
                foreach ($files as $file) {
                    $src[] = $this->upload->upload(file_get_contents($file),'binary','.xlsx');
                }
            }else {
                // 上传单文件
                $src = $this->upload->upload(file_get_contents($files),'binary','.xlsx');
            }
            return success(['src' => $src]);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }


    /**
     * 上传图标
     */
    public function iconFile($name)
    {
        $files = $this->request->file($name);
        if(empty($files)) return '';
        // 上传操作
        if(is_array($files)) {
            // 上传多文件
            $src = [];
            foreach ($files as $file) {
                $src[] = $this->upload->upload(file_get_contents($file),'binary','.png');
            }
        }else {
            // 上传单文件
            $src = $this->upload->upload(file_get_contents($files),'binary','.png');
        }
        return $src;
    }

    /**
     * 上传mp3文件
     * @return bool|string
     */
    public function mp3(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传操作
            if(is_array($files)) {
                // 上传多文件
                $src = [];
                foreach ($files as $file) {
                    $src[] = $this->upload->upload(file_get_contents($file),'binary','.mp3');
                }
            }else {
                // 上传单文件
                $src = $this->upload->upload(file_get_contents($files),'binary','.mp3');
            }
            return success($src);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传mp4文件
     * @return bool|string
     */
    public function mp4(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传操作
            if(is_array($files)) {
                // 上传多文件
                $src = [];
                foreach ($files as $file) {
                    $src[] = $this->upload->upload(file_get_contents($file),'binary','.mp4');
                }
            }else {
                // 上传单文件
                $src = $this->upload->upload(file_get_contents($files),'binary','.mp4');
            }
            return success($src);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传txt文件
     * @return bool|string
     */
    public function txt(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传操作
            if(is_array($files)) {
                // 上传多文件
                $src = [];
                foreach ($files as $file) {
                    $src[] = $this->upload->upload(file_get_contents($file),'binary','.txt');
                }
            }else {
                // 上传单文件
                $src = $this->upload->upload(file_get_contents($files),'binary','.txt');
            }
            return success($src);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 读取文件内容
     * @return bool|string
     */
    public function readFile(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传操作
            if(is_array($files)) {
                // 上传多文件
                $src = [];
                foreach ($files as $file) {
                    $src[] = file_get_contents($file);
                }
            }else {
                // 上传单文件
                $src = file_get_contents($files);
            }
            return success($src);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传ppt文件
     * @return bool|string
     */
    public function upload_ppt(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传操作
            if(is_array($files)) {
                // 上传多文件
                $src = [];
                foreach ($files as $file) {
                    $src[] = $this->upload->upload(file_get_contents($file),'binary','.ppt');
                }
            }else {
                // 上传单文件
                $src = $this->upload->upload(file_get_contents($files),'binary','.ppt');
            }
            return success($src);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 接收切片
     * @return bool|string
     */
    public function chunk(): bool|string
    {
        try {
            $file = $this->request->file('file');
            $param = $this->request->param();
            $this->validate($param,[
                'num|分片号' => 'require|integer',
                'save_name|保存地址' => 'require',
                'upload_id|上传ID' => 'require'
            ]);
            switch (env("UPLOAD_TYPE")) {
                case 'local':
                    // 保存切片到临时目录
                    $savePath = public_path('uploads/tmp/'.$param['upload_id']);
                    if(!is_dir($savePath)){
                        mkdir($savePath, 0755, true);
                    }
                    $file->move($savePath, $param['num']);
                    break;
                case 'oss':
                    return $this->ossV2ChunkUpload($file,$param['upload_id'],$param['num'],$param['save_name']);
                default:
                    throw new \Exception('参数错误！');
            }
            return success();
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 合并切片
     * @return bool|string
     */
    public function merge(): bool|string
    {
        try {
            $param = $this->request->param();
            switch (env("UPLOAD_TYPE")) {
                case 'local':
                    $this->validate($param,[
//                        'part|分片数组' => 'require',
                        'save_name|保存地址' => 'require',
                        'upload_id|上传ID' => 'require'
                    ]);
                    $tmpPath = public_path('uploads/tmp/'.$param['upload_id']);
                    // 创建合并后的临时文件
                    $save_name = './'.$param['save_name'];
                    $file_path  = dirname($save_name);
                    folder_build($file_path);
                    if (!$this->combineChunks($tmpPath, $save_name)){
                        throw new \Exception('合成失败！');
                    }
                    chmod($save_name, 0755);
                    // 清理临时文件
                    array_map('unlink', glob("$tmpPath/*"));
                    rmdir($tmpPath);
                    break;
                case 'oss':
                    $this->validate($param,[
                        'part|分片数组' => 'require',
                        'save_name|保存地址' => 'require',
                        'upload_id|上传ID' => 'require'
                    ]);
                    return $this->ossV2CompleteMultipartUpload($param['upload_id'],$param['part'],$param['save_name']);
                default:
                    throw new \Exception('参数错误！');
            }
            return success(['path' => $save_name]);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 本地将分片合成文件
     * @param $chunkDir
     * @param $destFile
     * @return bool
     */
    private function combineChunks($chunkDir, $destFile): bool
    {
        // 1. 更可靠的切片文件获取与排序
        $files = glob(rtrim($chunkDir, '/') . '/*');
        if ($files === false) {
            return false;
        }

        // 按数字顺序排序（假设文件名是纯数字或包含数字序号）
        usort($files, function($a, $b) {
            preg_match('/(\d+)/', basename($a), $m1);
            preg_match('/(\d+)/', basename($b), $m2);
            return (int)$m1[1] - (int)$m2[1];
        });

        // 2. 安全打开目标文件
        $fp = fopen($destFile, 'wb');
        if ($fp === false) {
            return false;
        }

        try {
            foreach ($files as $chunk) {
                // 检查切片文件是否存在且可读
                if (!is_file($chunk) || !is_readable($chunk)) {
                    return false;
                }

                // 3. 流式处理大文件（避免一次性加载到内存）
                $chunkFp = fopen($chunk, 'rb');
                if ($chunkFp === false) {
                    return false;
                }

                // 4. 分块写入，确保二进制安全
                while (!feof($chunkFp)) {
                    $buffer = fread($chunkFp, 4096); // 4KB缓冲区
                    if ($buffer === false) {
                        return false;
                    }
                    $written = fwrite($fp, $buffer);
                    if ($written === false || $written !== strlen($buffer)) {
                        return false;
                    }
                }

                fclose($chunkFp);
            }
        } finally {
            // 确保文件资源总是被关闭
            fclose($fp);
        }
        return true;
    }


    /**
     * 获取分片上传ID
     * @return bool|string
     */
    public function getUploadId(): bool|string
    {
        try {
            $param = $this->request->param();
            $this->validate($param,[
                'ext|文件后缀' => 'require'
            ]);
            $ext = explode('.',$param['ext']);
            $save_name = 'upload/'.date('Ym').'/'.date('d').'_'.time().uniqid().'.'.$ext[count($ext)-1];
            switch (env("UPLOAD_TYPE")) {
                case 'local':
                    $uploadId = time().uniqid();//标识符
                break;
                case 'oss':
                    return $this->getV2UploadId();
//                    $config     = config('app.oss');
//                    $ossClient  = new \OSS\OssClient($config['access_key_id'], $config['access_key_secret'], $config['upload_url'], false);
//                    $initOptions = array(
//                        $ossClient::OSS_HEADERS  => array(
//                            // 指定该Object被下载时的网页缓存行为。
//                            // 'Cache-Control' => 'no-cache',
//                            // 指定该Object被下载时的名称。
//                            // 'Content-Disposition' => 'attachment;filename=oss_download.jpg',
//                            // 指定过期时间，单位为毫秒。
//                            // 'Expires' => 150,
//                            // 指定初始化分片上传时是否覆盖同名Object。此处设置为true，表示禁止覆盖同名Object。
//                            //'x-oss-forbid-overwrite' => 'true',
//                            // 指定上传该Object的每个part时使用的服务器端加密方式。
//                            // 'x-oss-server-side-encryption'=> 'KMS',
//                            // 指定Object的加密算法。
//                            // 'x-oss-server-side-data-encryption'=>'SM4',
//                            // 指定KMS托管的用户主密钥。
//                            //'x-oss-server-side-encryption-key-id' => '9468da86-3509-4f8d-a61e-6eab1eac****',
//                            // 指定Object的存储类型。
//                            // 'x-oss-storage-class' => 'Standard',
//                            // 指定Object的对象标签，可同时设置多个标签。
//                            // 'x-oss-tagging' => 'TagA=A&TagB=B',
//                        ),
//                    );
//                    $uploadId = $ossClient->initiateMultipartUpload($config['bucket_name'], $save_name, $initOptions);
                    break;
                default:
                    throw new \Exception('参数错误！');
            }
            return success([
                'upload_id' => $uploadId,
                'save_name' => $save_name,
            ]);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传分片
     * @param $file
     * @param $upload_id
     * @param $num
     * @param $save_name
     * @return bool|string
     */
    public function ossChunkUpload($file,$upload_id,$num,$save_name): bool|string
    {
        try {
            $config     = config('app.oss');
            $ossClient  = new \OSS\OssClient($config['access_key_id'], $config['access_key_secret'], $config['upload_url'], false);
            $upOptions = array(
                // 上传文件。
                $ossClient::OSS_FILE_UPLOAD => $file,
                // 设置分片号。
                $ossClient::OSS_PART_NUM => $num,
                // 指定分片上传起始位置。
                $ossClient::OSS_SEEK_TO => 0,
                // 指定文件长度。
                $ossClient::OSS_LENGTH => filesize($file),
            );
            $data = $ossClient->uploadPart($config['bucket_name'], $save_name, $upload_id, $upOptions);
            return success($data);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 完成分片上传
     * @param $upload_id
     * @param $part
     * @param $save_name
     * @return bool|string
     */
    public function ossCompleteMultipartUpload($upload_id,$part,$save_name): bool|string
    {
        try {
            // $uploadParts是由每个分片的ETag和分片号（PartNumber）组成的数组。
            $uploadParts = [];
            foreach ($part as $i => $eTag) {
                $uploadParts[] = [
                    'PartNumber' => ($i + 1),
                    'ETag' => str_replace('"', '', $eTag),
                ];
            }
            /**
             * 步骤3：完成上传。
             */
            $comOptions['headers'] = array(
                // 指定完成分片上传时是否覆盖同名Object。此处设置为true，表示禁止覆盖同名Object。
                // 'x-oss-forbid-overwrite' => 'true',
                // 如果指定了x-oss-complete-all:yes，则OSS会列举当前uploadId已上传的所有Part，然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
                // 'x-oss-complete-all'=> 'yes'
            );
            // 执行completeMultipartUpload操作时，需要提供所有有效的$uploadParts。OSS收到提交的$uploadParts后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            $config     = config('app.oss');
            $ossClient  = new \OSS\OssClient($config['access_key_id'], $config['access_key_secret'], $config['upload_url'], false);
            $ossClient->completeMultipartUpload($config['bucket_name'], $save_name, $upload_id, $uploadParts, $comOptions);
            return success(['path' => $config['static_url'].'/'.$save_name]);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 本地文件分片上传
     * @param $file
     * @param $save_name
     * @return bool|string
     */
    public function multipartUpload($file,$save_name): bool|string
    {
        try {
            $config     = config('app.oss');
            $ossClient  = new \OSS\OssClient($config['access_key_id'], $config['access_key_secret'], $config['upload_url'], false);
            $options = array(
                $ossClient::OSS_CHECK_MD5 => true,
                // 分片大小（单位：MB），默认提升到 5MB，可通过 config('app.oss.part_size_mb') 配置
                $ossClient::OSS_PART_SIZE => (int) (config('app.oss.part_size_mb') ?? 5),
            );
            $ossClient->multiuploadFile($config['bucket_name'], $save_name, $file, $options);
            return success(['path' => $config['static_url'].'/'.$save_name]);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }


    /**
     * 上传mp3文件
     * @return bool|string
     */
    public function mp3_2(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            $getID3 = new getID3;
            // 上传操作
            if(is_array($files)) {
                // 上传多文件
                $src = [];
                foreach ($files as $file) {
                    $data = [];
                    $data['url'] = $this->upload->upload($file,'binary','.mp3');
                    $fileInfo = $getID3->analyze($file);
                    $data['duration'] = $fileInfo['playtime_seconds']??0;
                    $src[] = $data;
                }
            }else {
                // 上传单文件
                $src['url'] = $this->upload->upload($files,'binary','.mp3');
                $fileInfo = $getID3->analyze($files);
                $src['duration'] = $fileInfo['playtime_seconds']??0;
            }
            return success($src);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 上传通用文件
     * @return bool|string
     */
    public function file(): bool|string
    {
        try {
            $files = $this->request->file('file');
            if(empty($files)) throw new \Exception('请选择文件');
            // 上传单文件
            if (is_array($files)){
                $src = [];
                foreach ($files as $file){
                    $ext = $file->getOriginalExtension();
                    $src[] = $this->upload->upload($file,'base64','.'.$ext);
                }
            }else{
                $ext = $files->getOriginalExtension();
                $src = $this->upload->upload($files,'base64','.'.$ext);
            }
            return success($src);
        } catch (\Exception $e){
            return error($e->getMessage(), $e->getCode(),[
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
        }
    }

    /**
     * 转pdf
     * @return bool|string
     */
//    public function convertToPDF(): bool|string
//    {
//        try {
//            $param = $this->request->param();
//            $this->validate($param,[
//                'path|文件地址' => 'require',
//            ]);
//            switch (env('UPLOAD_TYPE')){
//                case 'local':
//                    $param['path2'] = substr($param['path'],1);
//                    if (!file_exists(app()->getRootPath().'public'.$param['path2'])) {
//                        throw new \Exception('文件不存在！');
//                    }
//                    WordToPdf(app()->getRootPath().'public'.$param['path2']);
//                    $ext = pathinfo(app()->getRootPath().'public'.$param['path2'], PATHINFO_EXTENSION);
//                    $path = str_replace($ext, 'pdf', $param['path']);
//                    break;
//                case 'oss':
//                    $ext = pathinfo($param['path'], PATHINFO_EXTENSION);
//                    $save_name = '/upload/'.date('Ym').'/'.date('d').'_'.time().uniqid();
//                    $file_path  = dirname($save_name.$ext);
//                    folder_build($file_path);
//                    if (!file_put_contents($save_name . $ext, file_get_contents($param['path']))) throw new \Exception('文件加载失败！');
//                    WordToPdf(app()->getRootPath().'public'.$save_name);
//                    $path = str_replace($ext, 'pdf', app()->getRootPath().'public'.$save_name);
//                    $path = oss($path,'.pdf');
//                    break;
//                default:
//                    throw new \Exception('系统错误！');
//            }
//            return success([
//                'path' => $path,
//            ]);
//        } catch (\Exception $e){
//            return error($e->getMessage(), $e->getCode(),[
//                'line' => $e->getLine(),
//                'file' => $e->getFile()
//            ]);
//        }
//    }
}
